package io.kiki.stack.http.feign.SpringMvcContract;

import io.kiki.stack.http.feign.InvocationHandlerFactory;
import io.kiki.stack.http.feign.MethodHandler;
import io.kiki.stack.http.feign.Target;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.cache.interceptor.CacheInterceptor;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Optional;

/**
 * Allows Spring's @Cache* annotations to be declared on the feign client's methods.
 */
public class FeignCachingInvocationHandlerFactory implements InvocationHandlerFactory {

    private final InvocationHandlerFactory invocationHandlerFactory;

    private final CacheInterceptor cacheInterceptor;

    public FeignCachingInvocationHandlerFactory(InvocationHandlerFactory invocationHandlerFactory, CacheInterceptor cacheInterceptor) {
        this.invocationHandlerFactory = invocationHandlerFactory;
        this.cacheInterceptor = cacheInterceptor;
    }

    @Override
    public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
        final InvocationHandler invocationHandler = invocationHandlerFactory.create(target, dispatch);
        return (proxy, method, argsNullable) -> {
            Object[] args = Optional.ofNullable(argsNullable).orElseGet(() -> new Object[0]);
            return cacheInterceptor.invoke(new MethodInvocation() {
                @Override
                public Method getMethod() {
                    return method;
                }

                @Override
                public Object[] getArguments() {
                    return args;
                }

                @Override
                public Object proceed() throws Throwable {
                    return invocationHandler.invoke(proxy, method, args);
                }

                @Override
                public Object getThis() {
                    return target;
                }

                @Override
                public AccessibleObject getStaticPart() {
                    return method;
                }
            });
        };
    }

}
